/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Raytracing.BRDF;

import Raytracing.Constants;
import Raytracing.Ray;
import Raytracing.Sampling;
import processing.core.PImage;
import processing.core.PVector;


/**
 *
 * @author Matt
 */
public class PhongBRDF implements BRDF {

    PVector Rd = new PVector();
    PVector Rs = new PVector();
    float n = 20;
    float specularity;
    PImage texture;

    float sigma = 0.f;
    
    public PhongBRDF(PVector diffuseColor, float specularity, float shiny) {

        //find Rd and Rs so that Rd+Rs <=1
        Rd = diffuseColor.get();
        Rd.mult(1.f - specularity);
        Rs.set(1, 1, 1);
        Rs.mult(specularity);
        this.specularity = specularity;
        this.n = shiny;


    }

    public PVector diffuseReflectance(PVector incoming, PVector outgoing, PVector normal, float tu, float tv, PVector u, PVector v) {

        PVector ret;

        if (texture != null) {
            int ix = (int) (texture.width * tu);
            int iy = (int) (texture.height * tv);

            int tcolor = texture.pixels[iy * texture.width + ix];


            ret = new PVector((tcolor >> 16) & 0xff,
                    (tcolor >> 8) & 0xff,
                    (tcolor) & 0xff);


            ret.div(255.f);
            ret.mult(1.f - specularity);

        } else {
            ret = Rd.get();
        }

        //diffuse

        ret.div(Constants.PI);
        return ret;

    }

    public PVector specularReflectance(PVector incoming, PVector outgoing, PVector normal, float tu, float tv, PVector u, PVector v) {
        normal = new PVector(0, 0, 1);

                PVector ret;

        if (texture != null) {
            int ix = (int) (texture.width * tu);
            int iy = (int) (texture.height * tv);

            int tcolor = texture.pixels[iy * texture.width + ix];


            ret = new PVector((tcolor >> 16) & 0xff,
                    (tcolor >> 8) & 0xff,
                    (tcolor) & 0xff);


            ret.div(255.f);
            ret.mult(1.f - specularity);

        } else {
            ret = Rd.get();
        }

        //diffuse

        ret.div(Constants.PI);
        
        //specular
        float cosInNorm = incoming.absDot(normal);

        //generate the half vector
        PVector h = PVector.add(incoming, outgoing);
        h.normalize();

        //calculate the reflectance vector
        float r = cosInNorm;
        r *= 2;
        PVector norm = normal.get();
        norm.mult(r);
        PVector R = PVector.sub(incoming, norm);



        float cosNorm = outgoing.dot(R);
        cosNorm = (float) (Math.pow(cosNorm, n));
        PVector bs = PVector.mult(Rs, (cosNorm * (n + 2.f) / (2.f * Constants.PI)));

        ret.add(bs);
        return ret;
    }

    public PVector getReflectance(PVector incoming, PVector outgoing, PVector normal, float tu, float tv, PVector u, PVector v) {


        normal = new PVector(0, 0, 1);



        //specular
        float cosInNorm = incoming.absDot(normal);

        //generate the half vector
        PVector h = PVector.add(incoming, outgoing);
        h.normalize();

        //calculate the reflectance vector
        float r = cosInNorm;
        r *= 2;
        PVector norm = normal.get();
        norm.mult(r);
        PVector R = PVector.sub(incoming, norm);



        float cosNorm = outgoing.dot(R);
        cosNorm = (float) (Math.pow(cosNorm, n));
        PVector bs = PVector.mult(Rs, (cosNorm * (n + 2.f) / (2.f * Constants.PI)));

        return bs;

    }

    private float diffusePDF(PVector outgoing, Ray sample) {
        if (sample.direction.z * outgoing.z > sigma) {
            return 1.f / Constants.PI;
        } else {
            return 0;
        }
    }

    private float specularPDF(PVector outgoing, Ray sample,
            PVector normal, PVector BRDF,
            float tu, float tv, PVector u, PVector v) {

        float toRet = 0;
        
        PVector h = PVector.add(sample.direction, outgoing);
        h.normalize();

        float hdot = h.dot(outgoing);
        float cosThetaH = Math.abs(h.z);


        //check if in same hemisphere
        if (sample.direction.z * outgoing.z > sigma) {
            toRet =  (float) (((n + 1.f) * Math.pow(cosThetaH, n)) / (2.f * Constants.PI * 4.f * hdot));
        } else {

            toRet = 0f;
        }
        
        if (toRet < .001f) toRet = 0;
        
        return toRet;
    }

    public float sampleReflectance(PVector outgoing, Ray sample,
            PVector normal, PVector BRDF,
            float tu, float tv, PVector u, PVector v,
            float u1, float u2) {

        sample.bounceType = Ray.DIFFUSE;

        float p2 = specularity;
        float p1 = 1-p2;

        p1 = .5f;
        p2 = .5f;
        
        if (Math.random() < p1) {
            Sampling.cosineSampleHemisphere(sample.direction, u1, u2);
            if (sample.direction.z * outgoing.z > sigma) {
                BRDF.set(diffuseReflectance(sample.direction, outgoing, normal, tu, tv, u, v));
            } else {
                BRDF.set(0, 0, 0);
            }

        } else {

            float cosTheta = (float) Math.pow(u1, 1.f / (n + 1.f));
            float sinTheta = (float) Math.sqrt(Math.max(0, 1.f - cosTheta * cosTheta));
            float phi = u2 * Constants.TWO_PI;

            PVector H = new PVector();

            H.x = (float) (sinTheta * Math.cos(phi));
            H.y = (float) (sinTheta * Math.sin(phi));
            H.z = (float) (cosTheta);

           
            
            if (H.dot(outgoing) < 0.f) {
               // H.mult(-1.f);
                return 0;
            }

            
            float f = 2.f * outgoing.dot(H);
            H.mult(f);
            H.sub(outgoing);

            sample.direction.set(H);

            if (sample.direction.z * outgoing.z > sigma) {
                BRDF.set(specularReflectance(sample.direction, outgoing, normal, tu, tv, u, v));
            } else {
                return 0f;
            }

        }

        return p1 * diffusePDF(outgoing, sample) + p2 * specularPDF(outgoing, sample, normal, BRDF, tu, tv, u, v);

    }
}
